iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
Mobile Development

Android app 效能優化系列 第 14

Out of memory 記憶體不足

  • 分享至 

  • xImage
  •  

在 Android 造成 OOM 的原因主要有 2 個:

  • 用了太多的記憶體
  • 記憶體無法正常釋放

這一篇我們要來談談如何避免用了太多的記憶體。

處理大型圖片的載入

隨著行動裝置的解析度與相機功能越來越好,拍照的照片檔案也越來越大,可能一個檔案就有 2~3MB,載入至記憶體就很容易發生OOM。

使用 Glide 載入圖片

Android 官方建議大部分的情況,直接使用像 Glide 這樣的圖片載入 Open Source,幫你處理載入圖片、decode、記憶體、Cache 的管理。

加入 dependencies

dependencies {
  implementation 'com.github.bumptech.glide:glide:4.14.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.14.0'
}

載入圖片

Glide.with(this).load("imageurl").into(imageView)

注意圖片提供者是否有縮圖版本

Android 所提供的從相機拍照或從相簿取得照片功能,會有一個檔案較小的 thumbnail 的版本。在顯示多張縮圖的圖片時,就適合用 thumbnail 的版本來減少記憶體的使用。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        //取得thumbnail 小圖
				val imageBitmap = data.extras.get("data") as Bitmap
        imageView.setImageBitmap(imageBitmap)
    }
}

在記憶體中載入縮圖版本

如果我們自已要載入一張圖片至 bitmap,就要在將圖片載入至記憶體時先進行縮圖。假設要載入一張原始大小是 1024*1024 的圖片顯示在 200*200 的ImageView上。如果直接載入1024*1024 大小的圖片卻只顯示在 200*200 的畫面上,顯然是一種浪費。Android 的 BitmapFactory 提供了讓你在載入圖片到記憶體之前先取得圖片大小,方便我們計算實際上只需載入的圖片大小以減少使用記憶體。

步驟如下:

  1. 取得原始圖片解析度
  2. 計算原始圖片縮小倍數
  3. 將原圖以依縮小倍數載入至記憶體

1.取得圖片解析度

下方程式碼,在 mipmap 放了一張圖片 scenery.png。使用BitmapFactory.decodeResource 取得圖片的寬高。在這個階段只是取得圖片資訊,並不會將圖片載入記憶體。

val options = BitmapFactory.Options().apply {
    inJustDecodeBounds = true
}
//在圖片載入到記憶體前,先取得圖片的寬高
BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)
val imageHeight: Int = options.outHeight
val imageWidth: Int = options.outWidth
val imageType: String = options.outMimeType

2.計算原始圖片縮小倍數

將圖片的真實大小與 UI 上所要顯示的大小來計算圖片需縮小幾倍。

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {
        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize
}
  1. 將原圖依縮小倍數載入至記憶體

透過 options 設定取得的縮小倍數,再呼叫 BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)即可依指定的縮小倍數來載入圖片至記憶體。

//記算Size,縮小幾倍
options.inSampleSize = calculateInSampleSize(options, smallImageWidth, smallImageHeight)
options.inJustDecodeBounds = true
binding.imageView.setImageBitmap(
    BitmapFactory.decodeResource(resources, R.mipmap.scenery, options)
)

以 1024*1024 的圖片要放到 200*200 的 Imageview,算出來的縮小倍數會是 4,也就是實際上載入的圖片會是1024/4=256。這樣就不會載入過大的圖片而增加 OOM 的機率。

最後,有些情況 OOM 不是馬上就會發生,而是累積一段時間才爆發,所以有時在測試時不容易發現。在後續我們將介紹使用 Profiler 來分析記憶體使用,就可以更容易找出OOM。下一篇將再介紹另一個原因:記憶體無法正常釋放,也就是 Memory leak。


上一篇
Android 的記憶體效能優化
下一篇
Memory leak 記憶體洩漏
系列文
Android app 效能優化30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言